#include <CL/cl.h>
#include <stdio.h>
#include <stdlib.h>
#include <wtypes.h>
#include <Strsafe.h>
#include <string.h>
#include <string>
#include <vector>
#include <omp.h>
#include <cstdlib>
#include <time.h>	
#include <math.h>
#include "MyOCL.h"
#include "WriteLog.h"

const cl_context MyOCL::context = MyOCL::createContext();
const cl_device_id * MyOCL::devices = MyOCL::createDeviceId();
const cl_command_queue MyOCL::commandQueue = MyOCL::createCommandQueue();

// ------------------------------------
// create static const devises
// ------------------------------------
cl_device_id * MyOCL::createDeviceId()
{
	cl_int status = 0;
	cl_device_id * devicesLocal;
	size_t deviceListSize;

	// First, get the size of device list data 
	status = clGetContextInfo(MyOCL::context, 
		CL_CONTEXT_DEVICES, 
		0, 
		NULL, 
		&deviceListSize);
	if(status != CL_SUCCESS) 
	{  
		writeErrorToLog(status, "Getting Context Info 'deviceListSize' (clGetContextInfo)");
		return NULL;
	}

	// Detect OpenCL devices
	devicesLocal = (cl_device_id *)malloc(deviceListSize);
	if(devicesLocal == 0)
	{
		writeErrorToLog(-1, "No devices found");
		return NULL;
	}

	// Now, get the device list data 
	status = clGetContextInfo(
		MyOCL::context, 
		CL_CONTEXT_DEVICES, 
		deviceListSize, 
		devicesLocal, 
		NULL);
	if(status != CL_SUCCESS) 
	{ 
		writeErrorToLog(status, "Getting Context Info 'deviceList' (clGetContextInfo)");
		return NULL;
	}
	return devicesLocal;
}

// -------------------------------
// create static const context
// -------------------------------
cl_context MyOCL::createContext()
{
	cl_int status = 0;
	cl_context contextLocal;

	// Have a look at the available platforms and pick either
	// the AMD one if available or a reasonable default.

	cl_uint numPlatforms;
	cl_platform_id platform = NULL;
	// obtain the list of platforms available.
	status = clGetPlatformIDs(0, NULL, &numPlatforms);
	if(status != CL_SUCCESS)
	{
		writeErrorToLog(status, "Getting Platforms (clGetPlatformsIDs)");
		return NULL;
	}

	if(numPlatforms > 0)
	{
		cl_platform_id* platforms = (cl_platform_id *)malloc(numPlatforms*sizeof(cl_platform_id));
		status = clGetPlatformIDs(numPlatforms, platforms, NULL);
		if(status != CL_SUCCESS)
		{
			writeErrorToLog(status, "Getting Platform Ids (clGetPlatformsIDs)");
			return NULL;
		}
		for(unsigned int i=0; i < numPlatforms; ++i)
		{
			char pbuff[100];
			// get specific information about the OpenCL platform
			status = clGetPlatformInfo(
				platforms[i],
				CL_PLATFORM_VENDOR,
				sizeof(pbuff),
				pbuff,
				NULL);
			if(status != CL_SUCCESS)
			{
				writeErrorToLog(status, "Getting Platform Info (clGetPlatformInfo)");
				return NULL;
			}
			platform = platforms[i];
			if(!strcmp(pbuff, "Advanced Micro Devices, Inc."))
			{
				break;
			}
		}
		delete platforms;
	}

	if(NULL == platform)
	{
		writeErrorToLog(-1000, "NULL platform found so Exiting Application");
		return NULL;
	}


	//If we could find our platform, use it. Otherwise use just available platform.

	cl_context_properties cps[3] = { CL_CONTEXT_PLATFORM, (cl_context_properties)platform, 0 };

	// Create an OpenCL context
	contextLocal = clCreateContextFromType(cps, 
		CL_DEVICE_TYPE_GPU,
		NULL, 
		NULL, 
		&status);
	if(status != CL_SUCCESS) 
	{  
		writeErrorToLog(status, "Creating Context (clCreateContextFromType)");
		return NULL; 
	}

	return contextLocal;
}

// -----------------------------------------
// create static const command queue
// -----------------------------------------
cl_command_queue MyOCL::createCommandQueue()
{
	cl_int status = 0;
	cl_command_queue commandQueueLocal;

	// Create an OpenCL command queue
	commandQueueLocal = clCreateCommandQueue(
		context, 
		MyOCL::devices[0], 
		0, 
		&status);
	if(status != CL_SUCCESS) 
	{ 
		writeErrorToLog(status, "Creating Command Queue 'commandQueueLocal'(clCreateCommandQueue)");
		return NULL;
	}
	return commandQueueLocal;
}

void MyOCL::cleanupCL()
{
	cl_int status;

	status = clReleaseCommandQueue(commandQueue);
	if(status != CL_SUCCESS)
	{
		writeErrorToLog(status, "Can't release command queue 'commandQueue' (clReleaseCommandQueue)");
		return;
	}

	status = clReleaseContext(context);
	if(status != CL_SUCCESS)
	{
		writeErrorToLog(status, "Can't release context 'context' (clReleaseContext)");
		return;
	}
}

void MyOCL::writeErrorToLog(cl_int status, QString messageError, QString fileError/* ="" */)
{
	// print name of error
	QString errorCode;

	switch(status)
	{
	case CL_DEVICE_NOT_FOUND:
		errorCode = "CL_DEVICE_NOT_FOUND";
		break;
	case CL_DEVICE_NOT_AVAILABLE:
		errorCode = "CL_DEVICE_NOT_AVAILABLE";
		break;
	case CL_COMPILER_NOT_AVAILABLE:
		errorCode = "CL_COMPILER_NOT_AVAILABLE";
		break;
	case CL_MEM_OBJECT_ALLOCATION_FAILURE:
		errorCode = "CL_MEM_OBJECT_ALLOCATION_FAILURE";
		break;
	case CL_OUT_OF_RESOURCES:
		errorCode = "CL_OUT_OF_RESOURCES";
		break;
	case CL_OUT_OF_HOST_MEMORY:
		errorCode = "CL_OUT_OF_HOST_MEMORY";
		break;
	case CL_PROFILING_INFO_NOT_AVAILABLE:
		errorCode = "CL_PROFILING_INFO_NOT_AVAILABLE";
		break;
	case CL_MEM_COPY_OVERLAP:
		errorCode = "CL_MEM_COPY_OVERLAP";
		break;
	case CL_IMAGE_FORMAT_MISMATCH:
		errorCode = "CL_IMAGE_FORMAT_MISMATCH";
		break;
	case CL_IMAGE_FORMAT_NOT_SUPPORTED:
		errorCode = "CL_IMAGE_FORMAT_NOT_SUPPORTED";
		break;
	case CL_BUILD_PROGRAM_FAILURE:
		errorCode = "CL_BUILD_PROGRAM_FAILURE";
		break;
	case CL_MAP_FAILURE:
		errorCode = "CL_MAP_FAILURE";
		break;

	case CL_INVALID_VALUE:
		errorCode = "CL_INVALID_VALUE";
		break;
	case CL_INVALID_DEVICE_TYPE:
		errorCode = "CL_INVALID_DEVICE_TYPE";
		break;
	case CL_INVALID_PLATFORM:
		errorCode = "CL_INVALID_PLATFORM";
		break;
	case CL_INVALID_DEVICE:
		errorCode = "CL_INVALID_DEVICE";
		break;
	case CL_INVALID_CONTEXT:
		errorCode = "CL_INVALID_CONTEXT";
		break;
	case CL_INVALID_QUEUE_PROPERTIES:
		errorCode = "CL_INVALID_QUEUE_PROPERTIES";
		break;
	case CL_INVALID_COMMAND_QUEUE:
		errorCode = "CL_INVALID_COMMAND_QUEUE";
		break;
	case CL_INVALID_HOST_PTR:
		errorCode = "CL_INVALID_HOST_PTR";
		break;
	case CL_INVALID_MEM_OBJECT:
		errorCode = "CL_INVALID_MEM_OBJECT";
		break;
	case CL_INVALID_IMAGE_FORMAT_DESCRIPTOR:
		errorCode = "CL_INVALID_IMAGE_FORMAT_DESCRIPTOR";
		break;
	case CL_INVALID_IMAGE_SIZE:
		errorCode = "CL_INVALID_IMAGE_SIZE";
		break;
	case CL_INVALID_SAMPLER:
		errorCode = "CL_INVALID_SAMPLER";
		break;
	case CL_INVALID_BINARY:
		errorCode = "CL_INVALID_BINARY";
		break;
	case CL_INVALID_BUILD_OPTIONS:
		errorCode = "CL_INVALID_BUILD_OPTIONS";
		break;
	case CL_INVALID_PROGRAM:
		errorCode = "CL_INVALID_PROGRAM";
		break;
	case CL_INVALID_PROGRAM_EXECUTABLE:
		errorCode = "CL_INVALID_PROGRAM_EXECUTABLE";
		break;
	case CL_INVALID_KERNEL_NAME:
		errorCode = "CL_INVALID_KERNEL_NAME";
		break;
	case CL_INVALID_KERNEL_DEFINITION:
		errorCode = "CL_INVALID_KERNEL_DEFINITION";
		break;
	case CL_INVALID_KERNEL:
		errorCode = "CL_INVALID_KERNEL";
		break;
	case CL_INVALID_ARG_INDEX:
		errorCode = "CL_INVALID_ARG_INDEX";
		break;
	case CL_INVALID_ARG_VALUE:
		errorCode = "CL_INVALID_ARG_VALUE";
		break;
	case CL_INVALID_ARG_SIZE:
		errorCode = "CL_INVALID_ARG_SIZE";
		break;
	case CL_INVALID_KERNEL_ARGS:
		errorCode = "CL_INVALID_KERNEL_ARGS";
		break;
	case CL_INVALID_WORK_DIMENSION:
		errorCode = "CL_INVALID_WORK_DIMENSION";
		break;
	case CL_INVALID_WORK_GROUP_SIZE:
		errorCode = "CL_INVALID_WORK_GROUP_SIZE";
		break;
	case CL_INVALID_WORK_ITEM_SIZE:
		errorCode = "CL_INVALID_WORK_ITEM_SIZE";
		break;
	case CL_INVALID_GLOBAL_OFFSET:
		errorCode = "CL_INVALID_GLOBAL_OFFSET";
		break;
	case CL_INVALID_EVENT_WAIT_LIST:
		errorCode = "CL_INVALID_EVENT_WAIT_LIST";
		break;
	case CL_INVALID_EVENT:
		errorCode = "CL_INVALID_EVENT";
		break;
	case CL_INVALID_OPERATION:
		errorCode = "CL_INVALID_OPERATION";
		break;
	case CL_INVALID_GL_OBJECT:
		errorCode = "CL_INVALID_GL_OBJECT";
		break;
	case CL_INVALID_BUFFER_SIZE:
		errorCode = "CL_INVALID_BUFFER_SIZE";
		break;
	case CL_INVALID_MIP_LEVEL:
		errorCode = "CL_INVALID_MIP_LEVEL";
		break;
	case CL_INVALID_GLOBAL_WORK_SIZE:
		errorCode = "CL_INVALID_GLOBAL_WORK_SIZE";
		break;
	default:
		break;
	}
	
	QString errorLabel = "Error: ";
	WriteLog::writeToLog(messageError, errorLabel.append(errorCode), fileError);
	// print log information
	/*char programLog[1024];
	status = clGetProgramBuildInfo(program, devices[0], CL_PROGRAM_BUILD_LOG, 1024, programLog, 0);
	cout << programLog << endl;
	*/
}

string MyOCL::convertToString(const char * filename)
{
	size_t size;
	char * str;
	string code;

	fstream file(filename, (fstream::in | fstream::binary));

	if(file.is_open())
	{
		size_t fileSize;
		file.seekg(0, fstream::end);
		size=fileSize=file.tellg();
		file.seekg(0, fstream::beg);

		str = new char[size+1];
		if(!str)
		{
			file.close();
			return NULL;
		}

		file.read(str, fileSize);
		file.close();
		str[size] = '\0';

		code = str;

		return code;
	}
	return NULL;
}